#include "espsettings.h"
#include "map"

#define DEFAULT_ESP_STRING ""

/**
 * @brief 配置复位标记，持久化中配置记录和此处不一致就按默认值复位
 *
 */
#define SETTINGS_VERSION_PREFIX "ESPWEB"
#define DEFAULT_SETTINGS_VERSION SETTINGS_VERSION_PREFIX SETTINGS_VERSION

#if ESP_SAVE_SETTINGS == SETTINGS_IN_EEPROM
#include <EEPROM.h>
// EEPROM SIZE (Up to 4096)
#define EEPROM_SIZE 2048 // max is 2048
#endif                   // SETTINGS_IN_EEPROM

/**
 * @brief 配置持久化默认项
 *
 */
std::map<settingKey, defaultSetting> defaultSettingMap = {
    // {KEY_SETTINGS_VERSION, {8, "ESPWEB" DEFAULT_SETTINGS_VERSION}}, // 7+1 byte,配置版本号，
    // {KEY_RADIO_MODE, {1, (const char *)DEFAULT_WIFI_MODE}},         // 1 byte,网络模式
    // {KEY_AP_SSID, {33, DEFAULT_AP_SSID}},                           // 32+1 byte,只支持英文
    // {KEY_AP_PASSWORD, {65, DEFAULT_AP_PASSWORD}},                   // 64+1 byte,只支持英文
    // {KEY_STA_SSID, {33, DEFAULT_STA_SSID}},                         // 32+1 byte,只支持英文
    // {KEY_STA_PASSWORD, {65, DEFAULT_STA_PASSWORD}},                 // 64+1 byte,只支持英文
};
int16_t pos = 0;
void Settings::init(settingKey key, int maxLen, const char *defaultValue)
{
    defaultSettingMap[key] = {maxLen, defaultValue, pos};
    pos += maxLen;
}
bool Settings::begin()
{
    // uint16_t pos = 0;
    // // 计算偏移量
    // for (auto it : defaultSettingMap)
    // {
    //     it.second.pos = pos;
    //     log_debug("key:%d,pos:%d", it.first, pos);

    //     pos += it.second.maxLen;
    // }
    init(KEY_SETTINGS_VERSION, 8, "ESPWEB" DEFAULT_SETTINGS_VERSION); // 7+1 byte,配置版本号，
    init(KEY_RADIO_MODE, 1, (const char *)DEFAULT_WIFI_MODE);         // 1 byte,网络模式
    init(KEY_AP_SSID, 33, DEFAULT_AP_SSID);                           // 32+1 byte,只支持英文
    init(KEY_AP_PASSWORD, 65, DEFAULT_AP_PASSWORD);                   // 64+1 byte,只支持英文
    init(KEY_STA_SSID, 33, DEFAULT_STA_SSID);                         // 32+1 byte,只支持英文
    init(KEY_STA_PASSWORD, 65, DEFAULT_STA_PASSWORD);                 // 64+1 byte,只支持英文
    init(KEY_HEATING_TEMPERATURE, 4, DEFAULT_HEATING_TEMPERATURE);
    init(KEY_PID_P, 11, DEFAULT_PID_P);
    init(KEY_PID_I, 11, DEFAULT_PID_I);
    init(KEY_PID_D, 11, DEFAULT_PID_D);

    log_debug("key:setv,pos:%d", defaultSettingMap[KEY_SETTINGS_VERSION].pos);
    log_debug("key:radio,pos:%d", defaultSettingMap[KEY_RADIO_MODE].pos);
    log_debug("key:apid,pos:%d", defaultSettingMap[KEY_AP_SSID].pos);
    log_debug("key:apwd,pos:%d", defaultSettingMap[KEY_AP_PASSWORD].pos);
    log_debug("key:staid,pos:%d", defaultSettingMap[KEY_STA_SSID].pos);
    log_debug("key:stapwd,pos:%d", defaultSettingMap[KEY_STA_PASSWORD].pos);
    log_debug("key:temp,pos:%d", defaultSettingMap[KEY_HEATING_TEMPERATURE].pos);
    if (pos > EEPROM_SIZE)
    {
        log_error("Out of EEPROM,EEPROM size %d,pos %d", EEPROM_SIZE, pos);
        return false;
    }
    String currentVersion = SETTINGS_VERSION;
    if (getSettingsVersion() != currentVersion.toInt())
    {
        return false;
    }
    return true;
}
// clear all entries
bool Settings::reset()
{
    bool res = true;
    log_debug("reset wifi setting");

    // wifi mode
    Settings::writeByte(KEY_RADIO_MODE, Settings::getDefaultSettingByte(KEY_RADIO_MODE));
    // STA SSID
    Settings::writeString(KEY_STA_SSID, Settings::getDefaultSettingString(KEY_STA_SSID).c_str());
    // STA pwd
    Settings::writeString(KEY_STA_PASSWORD, Settings::getDefaultSettingString(KEY_STA_PASSWORD).c_str());
    // AP SSID
    Settings::writeString(KEY_AP_SSID, Settings::getDefaultSettingString(KEY_AP_SSID).c_str());
    // AP password
    Settings::writeString(KEY_AP_PASSWORD, Settings::getDefaultSettingString(KEY_AP_PASSWORD).c_str());

    Settings::writeString(KEY_HEATING_TEMPERATURE, Settings::getDefaultSettingString(KEY_HEATING_TEMPERATURE).c_str());

    Settings::writeString(KEY_SETTINGS_VERSION, DEFAULT_SETTINGS_VERSION);

    Settings::writeString(KEY_PID_P, Settings::getDefaultSettingString(KEY_PID_P).c_str());
    Settings::writeString(KEY_PID_I, Settings::getDefaultSettingString(KEY_PID_I).c_str());
    Settings::writeString(KEY_PID_D, Settings::getDefaultSettingString(KEY_PID_D).c_str());

    return res;
}
defaultSetting Settings::getDefaultSetting(settingKey key)
{
    return defaultSettingMap[key];
}
uint8_t Settings::getDefaultSettingByte(settingKey key)
{
    return (uint32_t)defaultSettingMap[key].defaultValue;
}
String Settings::getDefaultSettingString(settingKey key)
{
    return defaultSettingMap[key].defaultValue;
}
uint32_t Settings::getDefaultSettingUint32(settingKey key)
{

    String val = defaultSettingMap[key].defaultValue;
    return val.toInt();
}

// write a flag / byte
bool Settings::writeByte(settingKey key, const uint8_t value)
{
    int pos = defaultSettingMap[key].pos;
    log_debug("writeByte:%d,pos:%d", value, pos);

#if ESP_SAVE_SETTINGS == SETTINGS_IN_EEPROM
    // check if parameters are acceptable
    if (pos + 1 > EEPROM_SIZE)
    {
        log_error("Error read byte %d", pos);
        return false;
    }
    EEPROM.begin(EEPROM_SIZE);
    EEPROM.write(pos, value);
    if (!EEPROM.commit())
    {
        log_error("Error commit %d", pos);
        return false;
    }
    EEPROM.end();
#endif // SETTINGS_IN_EEPROM
#if ESP_SAVE_SETTINGS == SETTINGS_IN_PREFERENCES
    Preferences prefs;
    if (!prefs.begin(NAMESPACE, false))
    {
        log_debug("Error opening %s", NAMESPACE);
        return false;
    }
    String p = "P_" + String(pos);
    uint8_t r = prefs.putChar(p.c_str(), value);
    prefs.end();
    if (r == 0)
    {
        log_debug("Error commit %s", p.c_str());
        return false;
    }
#endif // SETTINGS_IN_PREFERENCES
    return true;
}

// write a string (array of byte with a 0x00  at the end)
bool Settings::writeString(settingKey key, const char *byte_buffer)
{
    int size_buffer = strlen(byte_buffer);
    uint8_t size_max = defaultSettingMap[key].maxLen;
    int pos = defaultSettingMap[key].pos;
    // check if parameters are acceptable
    if (size_max == 0)
    {
        log_error("Error unknow entry %d", pos);
        return false;
    }
    if (size_max < size_buffer)
    {
        log_error("Error string too long %d, %d", pos, size_buffer);
        return false;
    }
#if ESP_SAVE_SETTINGS == SETTINGS_IN_EEPROM
    if (pos + size_buffer + 1 > EEPROM_SIZE || byte_buffer == NULL)
    {
        log_error("Error write string %d", pos);
        return false;
    }
    // copy the value(s)
    EEPROM.begin(EEPROM_SIZE);
    for (int i = 0; i < size_buffer; i++)
    {
        EEPROM.write(pos + i, byte_buffer[i]);
    }
    // 0 terminal
    EEPROM.write(pos + size_buffer, 0x00);
    if (!EEPROM.commit())
    {
        log_error("Error commit %d", pos);
        return false;
    }
    EEPROM.end();
#endif // SETTINGS_IN_EEPROM
#if ESP_SAVE_SETTINGS == SETTINGS_IN_PREFERENCES
    Preferences prefs;
    if (!prefs.begin(NAMESPACE, false))
    {
        log_debug("Error opening %s", NAMESPACE);
        return false;
    }
    String p = "P_" + String(pos);
    uint8_t r = prefs.putString(p.c_str(), byte_buffer);
    prefs.end();
    if (r != size_buffer)
    {
        log_debug("Error commit %s", p.c_str());
        return false;
    }
#endif // SETTINGS_IN_PREFERENCES
    return true;
}

// read a string
// a string is multibyte + \0, this is won't work if 1 char is multibyte like chinese char
const char *Settings::readString(settingKey key, bool *haserror)
{
    uint8_t size_max = defaultSettingMap[key].maxLen;
    int pos = defaultSettingMap[key].pos;
    if (haserror)
    {
        *haserror = true;
    }
    if (size_max == 0)
    {
        log_error("Error size string %d", pos);
        return DEFAULT_ESP_STRING;
    }
#if ESP_SAVE_SETTINGS == SETTINGS_IN_EEPROM
    static char *byte_buffer = NULL;
    size_max++; // do not forget the 0x0 for the end
    if (byte_buffer)
    {
        free(byte_buffer);
        byte_buffer = NULL;
    }
    // check if parameters are acceptable
    if (pos + size_max + 1 > EEPROM_SIZE)
    {
        log_error("Error read string %d", pos);
        return DEFAULT_ESP_STRING;
    }
    byte_buffer = (char *)malloc(size_max + 1);
    if (!byte_buffer)
    {
        log_error("Error mem read string %d", pos);
        return DEFAULT_ESP_STRING;
    }
    EEPROM.begin(EEPROM_SIZE);
    byte b = 1; // non zero for the while loop below
    int i = 0;

    // read until max size is reached or \0 is found
    while (i < size_max && b != 0)
    {
        b = EEPROM.read(pos + i);
        byte_buffer[i] = isPrintable(char(b)) ? b : 0;
        i++;
    }

    // Be sure there is a 0 at the end.
    if (b != 0)
    {
        byte_buffer[i - 1] = 0x00;
    }
    EEPROM.end();

    if (haserror)
    {
        *haserror = false;
    }
    return byte_buffer;

#endif // SETTINGS_IN_EEPROM
#if ESP_SAVE_SETTINGS == SETTINGS_IN_PREFERENCES
    Preferences prefs;
    static String res;

    if (!prefs.begin(NAMESPACE, true))
    {
        log_debug("Error opening %s", NAMESPACE);
        return "";
    }
    String p = "P_" + String(pos);
    if (prefs.isKey(p.c_str()))
    {
        res = prefs.getString(p.c_str(), get_default_string_value(pos));
    }
    else
    {
        res = get_default_string_value(pos);
    }
    prefs.end();

    if (res.length() > size_max)
    {
        log_debug("String too long %d vs %d", res.length(), size_max);
        res = res.substring(0, size_max - 1);
    }

    if (haserror)
    {
        *haserror = false;
    }
    return res.c_str();

#endif // SETTINGS_IN_PREFERENCES
}

// Get Settings Version
//  * -1 means no version detected
//  * 00 / 01 Not used
//  * 03 and up is version
int8_t Settings::getSettingsVersion()
{
    int8_t v = -1;
    String version = Settings::readString(KEY_SETTINGS_VERSION);
    if ((version == SETTINGS_VERSION_PREFIX) || ((int)version.length() != defaultSettingMap[KEY_SETTINGS_VERSION].maxLen - 1) || (version.indexOf(SETTINGS_VERSION_PREFIX) != 0))
    {
        log_debug("Invalid Settings Version %s", version.c_str());
        log_debug("default version %s", DEFAULT_SETTINGS_VERSION);
        return v;
    }
    v = version.substring(strlen(SETTINGS_VERSION_PREFIX)).toInt();
    log_debug("Settings Version %d", v);
    return v;
}
uint8_t Settings::readByte(settingKey key, bool *haserror)
{
    // uint8_t size_max = defaultSettingMap[key].maxLen;
    int pos = defaultSettingMap[key].pos;
    if (haserror)
    {
        *haserror = true;
    }
    uint8_t value = getDefaultSettingByte(key);
#if ESP_SAVE_SETTINGS == SETTINGS_IN_EEPROM
    // check if parameters are acceptable
    if ((pos + 1 > EEPROM_SIZE))
    {
        log_error("Error read byte %d", pos);
        return value;
    }
    // read byte
    EEPROM.begin(EEPROM_SIZE);
    value = EEPROM.read(pos);
    EEPROM.end();
#endif // SETTINGS_IN_EEPROM
#if ESP_SAVE_SETTINGS == SETTINGS_IN_PREFERENCES
    Preferences prefs;
    if (!prefs.begin(NAMESPACE, true))
    {
        log_debug("Error opening %s", NAMESPACE);
        return value;
    }
    String p = "P_" + String(pos);
    if (prefs.isKey(p.c_str()))
    {
        value = prefs.getChar(p.c_str(), get_default_byte_value(pos));
    }
    else
    {
        value = get_default_byte_value(pos);
    }
    prefs.end();
#endif // SETTINGS_IN_PREFERENCES
    if (haserror)
    {
        *haserror = false;
    }
    return value;
}

// read a uint32
uint32_t Settings::readUint32(settingKey key, bool *haserror)
{
    // uint8_t size_max = defaultSettingMap[key].maxLen;
    int pos = defaultSettingMap[key].pos;
    if (haserror)
    {
        *haserror = true;
    }
    uint32_t res = getDefaultSettingUint32(key);
#if ESP_SAVE_SETTINGS == SETTINGS_IN_EEPROM
    // check if parameters are acceptable
    uint8_t size_buffer = sizeof(uint32_t);
    if (pos + size_buffer > EEPROM_SIZE)
    {
        log_error("Error read int %d", pos);
        return res;
    }
    uint8_t i = 0;
    EEPROM.begin(EEPROM_SIZE);
    // read until max size is reached
    while (i < size_buffer)
    {
        ((uint8_t *)(&res))[i] = EEPROM.read(pos + i);
        i++;
    }
    EEPROM.end();
#endif // SETTINGS_IN_EEPROM
#if ESP_SAVE_SETTINGS == SETTINGS_IN_PREFERENCES
    Preferences prefs;
    if (!prefs.begin(NAMESPACE, true))
    {
        log_debug("Error opening %s", NAMESPACE);
        return res;
    }
    String p = "P_" + String(pos);
    if (prefs.isKey(p.c_str()))
    {
        res = prefs.getUInt(p.c_str(), res);
    }
    else
    {
        res = get_default_int32_value(pos);
    }
    prefs.end();
#endif // SETTINGS_IN_PREFERENCES
    if (haserror)
    {
        *haserror = false;
    }
    return res;
}

// write a uint32
bool Settings::writeUint32(settingKey key, const uint32_t value)
{
#if ESP_SAVE_SETTINGS == SETTINGS_IN_EEPROM
    int size_buffer = sizeof(uint32_t);
    // uint8_t size_max = defaultSettingMap[key].maxLen;
    int pos = defaultSettingMap[key].pos;
    // check if parameters are acceptable
    if (pos + size_buffer > EEPROM_SIZE)
    {
        log_error("Error invalid entry %d", pos);
        return false;
    }
    EEPROM.begin(EEPROM_SIZE);
    // copy the value(s)
    for (int i = 0; i < size_buffer; i++)
    {
        EEPROM.write(pos + i, ((uint8_t *)(&value))[i]);
    }
    if (!EEPROM.commit())
    {
        log_error("Error commit %d", pos);
        return false;
    }
    EEPROM.end();
#endif // SETTINGS_IN_EEPROM
#if ESP_SAVE_SETTINGS == SETTINGS_IN_PREFERENCES
    Preferences prefs;
    if (!prefs.begin(NAMESPACE, false))
    {
        log_esp3d("Error opening %s", NAMESPACE);
        return false;
    }
    String p = "P_" + String(pos);
    uint8_t r = prefs.putUInt(p.c_str(), value);
    prefs.end();
    if (r == 0)
    {
        log_esp3d("Error commit %s", p.c_str());
        return false;
    }
#endif // SETTINGS_IN_PREFERENCES
    return true;
}